package pa14.proj.boundschecker.main;


import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

import soot.G;
import soot.Unit;
import soot.Value;
import soot.ValueBox;
import soot.jimple.NewArrayExpr;
import soot.jimple.RetStmt;
import soot.jimple.ReturnStmt;
import soot.jimple.ReturnVoidStmt;
import soot.jimple.internal.RValueBox;
import soot.toolkits.graph.ExceptionalUnitGraph;
import soot.toolkits.scalar.ForwardFlowAnalysis;

public class BoundsCollectAnalysis extends ForwardFlowAnalysis<Unit, HashMap<Value, Integer>> {
	private boolean dl = false;
	private HashSet<Value> localArrayVars;
	private HashMap<Value, Integer> ranges;
	private HashMap<Value, Value> synonyms;
	
	
	public BoundsCollectAnalysis(ExceptionalUnitGraph graph, HashSet<Value> localArrayVars, boolean debuglevel) {
		super(graph);
		this.dl = debuglevel;
		this.ranges = new HashMap<Value, Integer>();
		this.synonyms = new HashMap<Value, Value>();
		this.localArrayVars = localArrayVars;
		if(dl){ print(" initiating boundscollect...", 0);}
		
		doAnalysis();
	}

	@Override
	protected void flowThrough(HashMap<Value, Integer> in, Unit thisStmt, HashMap<Value, Integer> out) {
		
		if(dl){ print(thisStmt.toString(), 1);}
		if(isCreationStatement(thisStmt)){
			print(" is a creation statement", 2);
			List<ValueBox> defVars = thisStmt.getDefBoxes();
			ValueBox definedVar = defVars.get(0);
			Integer definedIndexRange = getDefinedIndexRange(thisStmt);
			print(" got the defined range as: " + definedIndexRange, 2);
			//out.clear();
			//out.putAll(in);
			if(ranges.containsKey(definedVar.getValue())){
				Integer inRange = ranges.get(definedVar.getValue());
				Integer obtainedRange = definedIndexRange;
				if(obtainedRange < inRange){
					ranges.put(definedVar.getValue(), obtainedRange);
				}
			}else{
				ranges.put(definedVar.getValue(), definedIndexRange);
			}
		}
		
		if(isReturnStatement(thisStmt)){
			if(isNormalReturn(thisStmt)){
				print(" is a normal return", 2);
			}else{
				print(" not a normal return", 2);
			}
			

			//ranges.clear();
			//ranges.putAll(in);
			
			if(dl)print("Return detected! Printing the range values: (Size of range map: " + ranges.size() + ")", 2);
			if(dl)for(Value v: ranges.keySet()){
				print("|> " + v.toString() + "\t--> " +  ranges.get(v).toString(), 3);
			}
			
			if(dl)print("Return detected! Printing the synonyms values: (Size of range map: " + ranges.size() + ")", 2);
			if(dl)for(Value v: synonyms.keySet()){
				print("|> " + v.toString() + "\t--> " +  synonyms.get(v).toString(), 3);
			}
			
		}
		
		if(isArrayAssignment(thisStmt)){
			Value lValue = null, rValue = null;
			
			List<ValueBox> lValues = thisStmt.getDefBoxes();
			if(lValues.size() != 0){
				lValue = lValues.get(0).getValue();
			}
			List<ValueBox> rValues = thisStmt.getUseBoxes();
			if(rValues.size() != 0){
				rValue = rValues.get(0).getValue();
			}
			
			if(synonyms.get(rValue)!= null){
				synonyms.put(lValue, synonyms.get(rValue));
			}else if(lValue != null && rValue != null){
				synonyms.put(lValue, rValue);
			}
		}
		
	}

	private boolean isArrayAssignment(Unit thisStmt) {
		List<ValueBox> rValue = thisStmt.getUseBoxes();
		if(rValue.size() == 1){
			if(localArrayVars.contains(rValue.get(0).getValue())){
				return true;
			}
		}
		return false;
	}

	private boolean isNormalReturn(Unit thisStmt) {
		return (thisStmt instanceof ReturnVoidStmt);
	}

	private boolean isReturnStatement(Unit thisStmt) {
		return ((thisStmt instanceof RetStmt) || (thisStmt instanceof ReturnStmt) ||(thisStmt instanceof ReturnVoidStmt));
	}

	protected boolean isCreationStatement(Unit thisStmt){
		List<ValueBox> useBoxes = thisStmt.getUseBoxes();
		
		for(ValueBox useBox: useBoxes){
			if(useBox instanceof RValueBox){
				Value boxValue = useBox.getValue();
				if(boxValue instanceof NewArrayExpr){
					return true;
				}
			}
		}
		
		return false;
	}
	
	private Integer getDefinedIndexRange(Unit thisStmt) {
		ValueBox newArrayElement = thisStmt.getUseBoxes().get(0);
		Value value = newArrayElement.getValue();
		Integer newIndex = Integer.parseInt(value.toString());
		return newIndex;
	}
	
	@Override
	protected void copy(HashMap<Value, Integer> source, HashMap<Value, Integer> dest) {

		dest.clear();
		dest.putAll(source);
	}

	@Override
	protected HashMap<Value, Integer> entryInitialFlow() {
		return new HashMap<Value, Integer>();
	}

	@Override
	protected void merge(HashMap<Value, Integer> in1, HashMap<Value, Integer> in2, HashMap<Value, Integer> out) {
		out.clear();
		// TODO try to deal with test case 5 which deals with re-declaration of array values. 
		out.putAll(in1);
		out.putAll(in2);
	}

	@Override
	protected HashMap<Value, Integer> newInitialFlow() {
		return new HashMap<Value, Integer>();
	}

	public HashMap<Value, Integer> getRanges() {
		return this.ranges;
	}

	public HashMap<Value, Value> getSynonyms() {
		return this.synonyms;
	}
	
	protected void print(Object str, int n){
		while(n-- != 0){
			G.v().out.print("\t");
		}
		G.v().out.println(str);
	}

}
